package com.ly.common.util;

import com.google.gson.*;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.ly.common.biz.BizException;
import ognl.MemberAccess;
import ognl.Ognl;
import ognl.OgnlException;

import java.io.IOException;
import java.lang.reflect.*;
import java.sql.Timestamp;
import java.util.List;
import java.util.*;

public class Utils {

    /**
     * 深克隆 list
     * @param c
     * @param fields
     * @return
     */
    public static List cloneList(List c, String... fields) {
        List ret = new ArrayList();
        if (fields != null && fields.length > 0) {
            for (int i = 0; i < c.size(); i++) {
                ret.add(Utils.clone(c.get(i), fields));
            }
        }
        return ret;
    }

    /**
     * 深克隆
     * @param t
     * @param fields  包含或排除(前面加 - 号)的属性
     * @param <T>
     * @return
     */
    public static <T> T clone(T t, String... fields) {
        if (t == null) {
            return null;
        } else if (t instanceof Map) {
            // @TODO Map 集合暂未实现 ...
            return t;
        }
        boolean subMode = fields.length == 0 || fields[0].startsWith("-");
        try {
            @SuppressWarnings("unchecked")
            T tt = (T) t.getClass().newInstance();
            if (subMode) {
                // 减模式
                a:
                for (Field declaredField : t.getClass().getDeclaredFields()) {
                    if (Modifier.isStatic(declaredField.getModifiers())) continue;
                    for (String field : fields) {
                        if (field.matches("-?" + declaredField.getName())) {
                            continue a;
                        }
                    }
                    declaredField.setAccessible(true);
                    declaredField.set(tt, declaredField.get(t));
                }
            } else {
                // 加模式
                for (String f : fields) {
                    Field fd = t.getClass().getDeclaredField(f);
                    fd.setAccessible(true);
                    fd.set(tt, fd.get(t));
                }
            }
            return tt;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public static void test(boolean bool, String msg) throws BizException {
        if (bool) {
            throw new BizException(msg);
        }
    }

    /**
     * 返回参数中第一个非空对象
     * @param objs
     * @param <T>
     * @return
     */
    @SafeVarargs
    public static <T> T nvl(T... objs) {
        for (T o : objs) {
            if (o != null) {
                return o;
            }
        }
        return null;
    }

    /**
     * 设值对象属性值, 例如: setBean(对象, 名1,值1,名2,值2...)
     * @param t
     * @param fieldAndValues
     * @param <T>
     * @return
     */
    public static <T> T setBean(T t, Object... fieldAndValues) {
        @SuppressWarnings("rawtypes")
        Map ognlContent = Ognl.createDefaultContext(t, DEFAULT_MEMBER_ACCESS);
        for (int i = 0; i < fieldAndValues.length; i += 2) {
            if (i + 1 == fieldAndValues.length) {
                break;
            }
            try {
                Ognl.setValue(fieldAndValues[i].toString(), ognlContent, t, fieldAndValues[i + 1]);
            } catch (OgnlException e) {
                throw new RuntimeException(e);
            }
        }
        return t;
    }

    /**
     * 键值对转map
     * @param objs
     * @return
     */
    public static Map<Object, Object> map(Object... objs) {
        Map<Object, Object> ret = new HashMap<Object, Object>();
        for (int i = 0; i < objs.length; i += 2) {
            Object key = objs[i];
            Object val = i + 1 == objs.length ? null : objs[i + 1];
            ret.put(key, val);
        }
        return ret;
    }

    private final static DefaultMemberAccess DEFAULT_MEMBER_ACCESS = new DefaultMemberAccess(true);

    @SuppressWarnings("rawtypes")
    public static class DefaultMemberAccess implements MemberAccess {
        public boolean allowPrivateAccess = false;
        public boolean allowProtectedAccess = false;
        public boolean allowPackageProtectedAccess = false;

        public DefaultMemberAccess(boolean allowAllAccess) {
            this(allowAllAccess, allowAllAccess, allowAllAccess);
        }

        public DefaultMemberAccess(boolean allowPrivateAccess, boolean allowProtectedAccess,
                                   boolean allowPackageProtectedAccess) {
            super();
            this.allowPrivateAccess = allowPrivateAccess;
            this.allowProtectedAccess = allowProtectedAccess;
            this.allowPackageProtectedAccess = allowPackageProtectedAccess;
        }

        public boolean getAllowPrivateAccess() {
            return allowPrivateAccess;
        }

        public void setAllowPrivateAccess(boolean value) {
            allowPrivateAccess = value;
        }

        public boolean getAllowProtectedAccess() {
            return allowProtectedAccess;
        }

        public void setAllowProtectedAccess(boolean value) {
            allowProtectedAccess = value;
        }

        public boolean getAllowPackageProtectedAccess() {
            return allowPackageProtectedAccess;
        }

        public void setAllowPackageProtectedAccess(boolean value) {
            allowPackageProtectedAccess = value;
        }

        public Object setup(Map context, Object target, Member member, String propertyName) {
            Object result = null;

            if (isAccessible(context, target, member, propertyName)) {
                AccessibleObject accessible = (AccessibleObject) member;

                if (!accessible.isAccessible()) {
                    result = Boolean.FALSE;
                    accessible.setAccessible(true);
                }
            }
            return result;
        }

        public void restore(Map context, Object target, Member member, String propertyName, Object state) {
            if (state != null) {
                ((AccessibleObject) member).setAccessible(((Boolean) state).booleanValue());
            }
        }

        public boolean isAccessible(Map context, Object target, Member member, String propertyName) {
            int modifiers = member.getModifiers();
            boolean result = Modifier.isPublic(modifiers);
            if (!result) {
                if (Modifier.isPrivate(modifiers)) {
                    result = getAllowPrivateAccess();
                } else {
                    if (Modifier.isProtected(modifiers)) {
                        result = getAllowProtectedAccess();
                    } else {
                        result = getAllowPackageProtectedAccess();
                    }
                }
            }
            try {
                Field propertyField = member.getDeclaringClass().getDeclaredField(propertyName);
                if (propertyField.getType().getName().startsWith("java") == false) {
                    propertyField.setAccessible(true);
                    Object memberObj = propertyField.get(target);
                    if (memberObj == null) {
                        Constructor c = propertyField.getType().getConstructor();
                        memberObj = c.newInstance();
                        propertyField.set(target, memberObj);
                    }
                }
            } catch (NoSuchMethodException | SecurityException e) {
                e.printStackTrace();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            return result;
        }
    }

    /**
     * 对象转 map,
     * @param obj
     * @param excludeKeys 排除字段
     * @return
     */
    public static Map<String, Object> asMap(Object obj, String... excludeKeys) {
        Map<String, Object> ret = new HashMap<String, Object>();
        Set<String> set = new HashSet<>();
        Collections.addAll(set, excludeKeys);
        if (obj != null) {
            for (Field f : obj.getClass().getDeclaredFields()) {
                if (set.contains(f.getName())) {
                    continue;
                }
                String getter = "get" + Character.toUpperCase(f.getName().charAt(0)) + f.getName().substring(1);
                try {
                    Method getterMethod = obj.getClass().getMethod(getter);
                    ret.put(f.getName(), getterMethod.invoke(obj));
                } catch (NoSuchMethodException e) {
                    continue;
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return ret;
    }

    /**
     * 键值对转 map
     * @param objs
     * @return
     */
    public static Map<Object, Object> toMap(Object... objs) {
        Map<Object, Object> map = new HashMap<Object, Object>();
        for (int i = 0; i < objs.length - 1; i += 2) {
            map.put(objs[i], objs[i + 1]);
        }
        return map;
    }

    /**
     * 判断是否为空对象或空字符串或空集合
     * @param obj
     * @return
     */
    public static boolean isEmpty(Object obj) {
        return obj == null
                || (obj instanceof String && ((String) obj).trim().isEmpty())
                || (obj instanceof Collection && ((Collection) obj).isEmpty())
                || (obj instanceof Map && ((Map) obj).isEmpty());
    }

    /**
     * 对象转JSON字符串, timestamp => 时间戳
     * @param src
     * @param fieldOrClass 忽略的字段或属性类
     * @return
     */
    public static String json(Object src, Object... fieldOrClass) {
        if (src == null) {
            return null;
        } else {
            try {
                Gson gson = new GsonBuilder()
                        .addSerializationExclusionStrategy(new ExclusionStrategy() {
                            @Override
                            public boolean shouldSkipField(FieldAttributes f) {
                                for (Object foc : fieldOrClass) {
                                    if (foc instanceof String && f.getName().equals(foc)) {
                                        return true;
                                    }
                                }
                                return false;
                            }

                            @Override
                            public boolean shouldSkipClass(Class<?> clazz) {
                                for (Object foc : fieldOrClass) {
                                    if (foc instanceof Class && clazz.equals(foc)) {
                                        return true;
                                    }
                                }
                                return false;
                            }
                        })
                        .registerTypeAdapter(Timestamp.class, new TypeAdapter<Timestamp>() {
                            @Override
                            public void write(JsonWriter out, Timestamp value) throws IOException {
                                if (value == null) {
                                    out.value("");
                                } else {
                                    out.value(value.getTime());
                                }
                            }

                            @Override
                            public Timestamp read(JsonReader in) throws IOException {
                                return null;
                            }
                        }).create();
                return gson.toJson(src);
            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }
    }

    /**
     * 判断是否是 手机终端
     * @param userAgent
     * @return
     */
    public static boolean isMobileDevice(String userAgent) {
        String[] deviceArray = new String[]{"android", "mac os", "windows phone"};
        if (userAgent == null)
            return false;
        userAgent = userAgent.toLowerCase();
        for (int i = 0; i < deviceArray.length; i++) {
            if (userAgent.indexOf(deviceArray[i]) > 0) {
                return true;
            }
        }
        return false;
    }

    public static interface Caller<T> {
        T handle(Object... params) throws Exception;
    }

    public static <T> T call(Caller<T> caller, Object... params) {
        try {
            return caller.handle(params);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException("call error", e);
        } finally {
            for (Object p : params) {
                if (p instanceof AutoCloseable) {
                    try {
                        ((AutoCloseable) p).close();
                    } catch (Exception e) {
                        throw new RuntimeException("close error", e);
                    }
                }
            }
        }
    }

    public static interface Execer {
        void handle(Object... params) throws Exception;
    }

    public static void exec(Execer execer, Object... params) {
        call(p -> {
            execer.handle(p);
            return null;
        }, params);
    }

}
